package com.hubspot.baragon.service;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.retry.ExponentialBackoffRetry;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingClient;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.inject.Binder;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Named;
import com.hubspot.baragon.BaragonDataModule;
import com.hubspot.baragon.config.AuthConfiguration;
import com.hubspot.baragon.config.HttpClientConfiguration;
import com.hubspot.baragon.config.ZooKeeperConfiguration;
import com.hubspot.baragon.data.BaragonConnectionStateListener;
import com.hubspot.baragon.data.BaragonWorkerDatastore;
import com.hubspot.baragon.service.config.BaragonConfiguration;
import com.hubspot.baragon.service.config.ElbConfiguration;
import com.hubspot.baragon.service.config.SentryConfiguration;
import com.hubspot.baragon.service.elb.ApplicationLoadBalancer;
import com.hubspot.baragon.service.elb.ClassicLoadBalancer;
import com.hubspot.baragon.service.exceptions.BaragonExceptionNotifier;
import com.hubspot.baragon.service.healthcheck.ZooKeeperHealthcheck;
import com.hubspot.baragon.service.listeners.AbstractLatchListener;
import com.hubspot.baragon.service.listeners.ElbSyncWorkerListener;
import com.hubspot.baragon.service.listeners.RequestPurgingListener;
import com.hubspot.baragon.service.listeners.RequestWorkerListener;
import com.hubspot.baragon.service.managed.BaragonExceptionNotifierManaged;
import com.hubspot.baragon.service.managed.BaragonGraphiteReporterManaged;
import com.hubspot.baragon.service.managed.BaragonManaged;
import com.hubspot.baragon.service.managers.AgentManager;
import com.hubspot.baragon.service.managers.ElbManager;
import com.hubspot.baragon.service.managers.RequestManager;
import com.hubspot.baragon.service.managers.ServiceManager;
import com.hubspot.baragon.service.managers.StatusManager;
import com.hubspot.baragon.service.resources.BaragonResourcesModule;
import com.hubspot.baragon.service.worker.BaragonElbSyncWorker;
import com.hubspot.baragon.service.worker.BaragonRequestWorker;
import com.hubspot.baragon.service.worker.RequestPurgingWorker;
import com.hubspot.baragon.utils.JavaUtils;
import com.hubspot.dropwizard.guicier.DropwizardAwareModule;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import io.dropwizard.jetty.HttpConnectorFactory;
import io.dropwizard.server.SimpleServerFactory;
public class BaragonServiceModule extends DropwizardAwareModule<BaragonConfiguration> {
public static final String BARAGON_SERVICE_SCHEDULED_EXECUTOR = "baragon.service.scheduledExecutor";
public static final String BARAGON_SERVICE_HTTP_PORT = "baragon.service.http.port";
public static final String BARAGON_SERVICE_HOSTNAME = "baragon.service.hostname";
public static final String BARAGON_SERVICE_LOCAL_HOSTNAME = "baragon.service.local.hostname";
public static final String BARAGON_SERVICE_HTTP_CLIENT = "baragon.service.http.client";
public static final String BARAGON_MASTER_AUTH_KEY = "baragon.master.auth.key";
public static final String BARAGON_URI_BASE = "baragon.uri.base";
public static final String BARAGON_AWS_ELB_CLIENT_V1 = "baragon.aws.elb.client.v1";
public static final String BARAGON_AWS_ELB_CLIENT_V2 = "baragon.aws.elb.client.v2";
@Override
public void configure(Binder binder) {
binder.requireExplicitBindings();
binder.requireExactBindingAnnotations();
binder.requireAtInjectOnConstructors();
binder.install(new BaragonDataModule());
binder.install(new BaragonResourcesModule());
// Healthcheck
binder.bind(ZooKeeperHealthcheck.class).in(Scopes.SINGLETON);
binder.bind(BaragonExceptionNotifier.class).in(Scopes.SINGLETON);
// Managed
binder.bind(BaragonExceptionNotifierManaged.class).in(Scopes.SINGLETON);
binder.bind(BaragonGraphiteReporterManaged.class).in(Scopes.SINGLETON);
binder.bind(BaragonManaged.class).in(Scopes.SINGLETON);
// Managers
binder.bind(AgentManager.class).in(Scopes.SINGLETON);
binder.bind(ElbManager.class).in(Scopes.SINGLETON);
binder.bind(RequestManager.class).in(Scopes.SINGLETON);
binder.bind(ServiceManager.class).in(Scopes.SINGLETON);
binder.bind(StatusManager.class).in(Scopes.SINGLETON);
// Workers
binder.bind(BaragonElbSyncWorker.class).in(Scopes.SINGLETON);
binder.bind(BaragonRequestWorker.class).in(Scopes.SINGLETON);
binder.bind(RequestPurgingWorker.class).in(Scopes.SINGLETON);
binder.bind(ClassicLoadBalancer.class);
binder.bind(ApplicationLoadBalancer.class);
Multibinder<AbstractLatchListener> latchBinder = Multibinder.newSetBinder(binder, AbstractLatchListener.class);
latchBinder.addBinding().to(RequestWorkerListener.class).in(Scopes.SINGLETON);
latchBinder.addBinding().to(ElbSyncWorkerListener.class).in(Scopes.SINGLETON);
latchBinder.addBinding().to(RequestPurgingListener.class).in(Scopes.SINGLETON);
}
@Provides
public ZooKeeperConfiguration provideZooKeeperConfiguration(BaragonConfiguration configuration) {
return configuration.getZooKeeperConfiguration();
}
@Provides
public HttpClientConfiguration provideHttpClientConfiguration(BaragonConfiguration configuration) {
return configuration.getHttpClientConfiguration();
}
@Provides
@Named(BaragonDataModule.BARAGON_AGENT_REQUEST_URI_FORMAT)
public String provideAgentUriFormat(BaragonConfiguration configuration) {
return configuration.getAgentRequestUriFormat();
}
@Provides
@Named(BaragonDataModule.BARAGON_AGENT_BATCH_REQUEST_URI_FORMAT)
public String provideAgentBatchUriFormat(BaragonConfiguration configuration) {
return configuration.getAgentBatchRequestUriFormat();
}
@Provides
@Named(BaragonDataModule.BARAGON_AGENT_MAX_ATTEMPTS)
public Integer provideAgentMaxAttempts(BaragonConfiguration configuration) {
return configuration.getAgentMaxAttempts();
}
@Provides
@Named(BaragonDataModule.BARAGON_AGENT_REQUEST_TIMEOUT_MS)
public Long provideAgentMaxRequestTime(BaragonConfiguration configuration) {
return configuration.getAgentRequestTimeoutMs();
}
@Provides
public AuthConfiguration providesAuthConfiguration(BaragonConfiguration configuration) {
return configuration.getAuthConfiguration();
}
@Provides
public Optional<ElbConfiguration> providesElbConfiguration(BaragonConfiguration configuration) {
return configuration.getElbConfiguration();
}
@Provides
@Singleton
@Named(BARAGON_SERVICE_SCHEDULED_EXECUTOR)
public ScheduledExecutorService providesScheduledExecutor() {
return Executors.newScheduledThreadPool(4);
}
@Provides
@Singleton
@Named(BaragonDataModule.BARAGON_SERVICE_WORKER_LAST_START)
public AtomicLong providesWorkerLastStartAt() {
return new AtomicLong();
}
@Provides
@Singleton
@Named(BaragonDataModule.BARAGON_ELB_WORKER_LAST_START)
public AtomicLong providesElbWorkerLastStartAt() {
return new AtomicLong();
}
@Provides
@Singleton
@Named(BARAGON_SERVICE_HTTP_PORT)
public int providesHttpPortProperty(BaragonConfiguration config) {
SimpleServerFactory simpleServerFactory = (SimpleServerFactory) config.getServerFactory();
HttpConnectorFactory httpFactory = (HttpConnectorFactory) simpleServerFactory.getConnector();
return httpFactory.getPort();
}
@Provides
@Named(BARAGON_SERVICE_HOSTNAME)
public String providesHostnameProperty(BaragonConfiguration config) throws Exception {
return Strings.isNullOrEmpty(config.getHostname()) ? JavaUtils.getHostAddress() : config.getHostname();
}
@Provides
@Named(BARAGON_SERVICE_LOCAL_HOSTNAME)
public String providesLocalHostnameProperty(BaragonConfiguration config) {
if (!Strings.isNullOrEmpty(config.getHostname())) {
return config.getHostname();
}
try {
final InetAddress addr = InetAddress.getLocalHost();
return addr.getHostName();
} catch (UnknownHostException e) {
throw new RuntimeException("No local hostname found, unable to start without functioning local networking (or configured hostname)", e);
}
}
@Provides
@Singleton
@Named(BaragonDataModule.BARAGON_SERVICE_LEADER_LATCH)
public LeaderLatch providesServiceLeaderLatch(BaragonConfiguration config,
BaragonWorkerDatastore datastore,
@Named(BARAGON_SERVICE_HTTP_PORT) int httpPort,
@Named(BARAGON_SERVICE_HOSTNAME) String hostname) {
final String appRoot = ((SimpleServerFactory)config.getServerFactory()).getApplicationContextPath();
final String baseUri = String.format("http://%s:%s%s", hostname, httpPort, appRoot);
return datastore.createLeaderLatch(baseUri);
}
@Provides
@Named(BARAGON_MASTER_AUTH_KEY)
public String providesMasterAuthKey(BaragonConfiguration configuration) {
return configuration.getMasterAuthKey();
}
@Provides
@Named(BARAGON_URI_BASE)
String getBaragonUriBase(final BaragonConfiguration configuration) {
final String baragonUiPrefix = configuration.getUiConfiguration().getBaseUrl().or(((SimpleServerFactory) configuration.getServerFactory()).getApplicationContextPath());
return (baragonUiPrefix.endsWith("/")) ? baragonUiPrefix.substring(0, baragonUiPrefix.length() - 1) : baragonUiPrefix;
}
@Provides
@Singleton
@Named(BARAGON_SERVICE_HTTP_CLIENT)
public AsyncHttpClient providesHttpClient(HttpClientConfiguration config) {
AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder();
builder.setMaxRequestRetry(config.getMaxRequestRetry());
builder.setRequestTimeoutInMs(config.getRequestTimeoutInMs());
builder.setFollowRedirects(true);
builder.setConnectionTimeoutInMs(config.getConnectionTimeoutInMs());
builder.setUserAgent(config.getUserAgent());
return new AsyncHttpClient(builder.build());
}
@Provides
@Named(BARAGON_AWS_ELB_CLIENT_V1)
public AmazonElasticLoadBalancingClient providesAwsElbClientV1(Optional<ElbConfiguration> configuration) {
AmazonElasticLoadBalancingClient elbClient;
if (configuration.isPresent() && configuration.get().getAwsAccessKeyId() != null && configuration.get().getAwsAccessKeySecret() != null) {
elbClient = new AmazonElasticLoadBalancingClient(new BasicAWSCredentials(configuration.get().getAwsAccessKeyId(), configuration.get().getAwsAccessKeySecret()));
} else {
elbClient = new AmazonElasticLoadBalancingClient();
}
if (configuration.isPresent() && configuration.get().getAwsEndpoint().isPresent()) {
elbClient.setEndpoint(configuration.get().getAwsEndpoint().get());
}
if (configuration.isPresent() && configuration.get().getAwsRegion().isPresent()) {
elbClient.configureRegion(Regions.fromName(configuration.get().getAwsRegion().get()));
}
return elbClient;
}
@Provides
@Named(BARAGON_AWS_ELB_CLIENT_V2)
public com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancingClient providesAwsElbClientV2(Optional<ElbConfiguration> configuration) {
com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancingClient elbClient;
if (configuration.isPresent() && configuration.get().getAwsAccessKeyId() != null && configuration.get().getAwsAccessKeySecret() != null) {
elbClient = new com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancingClient(new BasicAWSCredentials(configuration.get().getAwsAccessKeyId(), configuration.get().getAwsAccessKeySecret()));
} else {
elbClient = new com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancingClient();
}
if (configuration.isPresent() && configuration.get().getAwsEndpoint().isPresent()) {
elbClient.setEndpoint(configuration.get().getAwsEndpoint().get());
}
if (configuration.isPresent() && configuration.get().getAwsRegion().isPresent()) {
elbClient.configureRegion(Regions.fromName(configuration.get().getAwsRegion().get()));
}
return elbClient;
}
@Singleton
@Provides
public CuratorFramework provideCurator(ZooKeeperConfiguration config, BaragonConnectionStateListener connectionStateListener) {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(config.getQuorum())
.sessionTimeoutMs(config.getSessionTimeoutMillis())
.connectionTimeoutMs(config.getConnectTimeoutMillis())
.retryPolicy(new ExponentialBackoffRetry(config.getRetryBaseSleepTimeMilliseconds(), config.getRetryMaxTries()))
.defaultData(new byte[0])
.build();
client.getConnectionStateListenable().addListener(connectionStateListener);
client.start();
return client.usingNamespace(config.getZkNamespace());
}
@Provides
@Singleton
public Optional<SentryConfiguration> sentryConfiguration(final BaragonConfiguration config) {
return config.getSentryConfiguration();
}
}